package pit

import (
	"context"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"runtime"
	"unsafe"
)

// 可变参数是空接口类型
func Pit1() {
	// 当参数的可变参数是空接口类型时，传人空接口的切片时需要注意参数展开的问题。
	var a = []interface{}{1, 2, 3}

	// 不管是否展开，编译器都无法发现错误，但是输出是不同的：
	fmt.Println(a)
	fmt.Println(a...)
}

// 数组是值传递
// 必要时需要使用切片(引用传递)
func Pit2() {
	x := [3]int{1, 2, 3}

	func(arr [3]int) {
		arr[0] = 7
		fmt.Println(arr)
	}(x)

	fmt.Println(x)
}

// 返回值被屏蔽
func Pit3() (err error) {
	if err = Bar(); err != nil {
		return
	}
	return
}

func Bar() error {
	return nil
}

// recover必须在defer函数中运行
//recover捕获的是祖父级调用时的异常，直接调用时无效：
//
//func main() {
//	recover()
//	panic(1)
//}
//直接defer调用也是无效：
//
//func main() {
//	defer recover()
//	panic(1)
//}
//defer调用时多层嵌套依然无效：
//
//func main() {
//	defer func() {
//		func() { recover() }()
//	}()
//	panic(1)
//}
//必须在defer函数中直接调用才有效：
//
//func main() {
//	defer func() {
//		recover()
//	}()
//	panic(1)
//}

//不能通过Sleep来回避并发中的问题

// 独占CPU导致其它Goroutine饿死
func Pit4() {
	runtime.GOMAXPROCS(1)

	go func() {
		for i := 0; i < 10; i++ {
			fmt.Println(i)
		}
	}()

	for {
	} // 占用CPU
}

// 解决办法1
//func Pit4() {
//	runtime.GOMAXPROCS(1)
//
//	go func() {
//		for i := 0; i < 10; i++ {
//			fmt.Println(i)
//		}
//	}()
//
//	for {
//		runtime.Gosched()
//	}
//}
// 解决办法2
//func Pit4() {
//	runtime.GOMAXPROCS(1)
//
//	go func() {
//		for i := 0; i < 10; i++ {
//			fmt.Println(i)
//		}
//		os.Exit(0)
//	}()
//
//	select{}
//}

// 不同Goroutine之间不满足顺序一致性内存模型
func Pit5() {
	var msg string
	var done bool

	go func() {
		msg = "hello, world"
		done = true
	}()

	for !done {
	}
	println(msg)
}

// 解决的办法是用显式同步：
//func Pit5() {
//	var msg string
//	var done = make(chan bool)
//
//	go func() {
//		msg = "hello, world"
//		done <- true
//	}()
//
//	<-done
//	println(msg)
//}

// 6、闭包错误引用同一个变量
func Pit6() {
	for i := 0; i < 5; i++ {
		defer func() {
			println(i)
		}()
	}
}

// 正确
//func Pit6() {
//	for i := 0; i < 5; i++ {
//		i := i
//		defer func() {
//			println(i)
//		}()
//	}
//
//	// 或者这样
//	for i := 0; i < 5; i++ {
//		defer func(i int) {
//			println(i)
//		}(i)
//	}
//}

// 7、在循环内部执行defer语句
func Pit7() {
	// 错误演示
	//for i := 0; i < 5; i++ {
	//	f, err := os.Open("/path/to/file")
	//	if err != nil {
	//		log.Fatal(err)
	//	}
	//	defer f.Close()
	//}

	// 正确演示
	for i := 0; i < 5; i++ {
		func() {
			f, err := os.Open("/path/to/file")
			if err != nil {
				log.Fatal(err)
			}
			defer f.Close()
		}()
	}
}

// 8、切片会导致整个底层数组被锁定
func Pit8() {
	// 错误演示
	//headerMap := make(map[string][]byte)
	//
	//for i := 0; i < 5; i++ {
	//	name := "/path/to/file"
	//	data, err := ioutil.ReadFile(name)
	//	if err != nil {
	//		log.Fatal(err)
	//	}
	//	headerMap[name] = data[:1]
	//}

	// 正确演示
	headerMap := make(map[string][]byte)

	for i := 0; i < 5; i++ {
		name := "/path/to/file"
		data, err := ioutil.ReadFile(name)
		if err != nil {
			log.Fatal(err)
		}
		headerMap[name] = append([]byte{}, data[:1]...)
	}
}

// 9、空指针和空接口不等价
//func Pit9() error{
//  // 比如返回了一个错误指针，但是并不是空的error接口：
//	var p MyError = nil
//	if bad() {
//		p = ErrBad
//	}
//	return p // Will always return a non-nil error.
//}

// 10、内存地址会变化
func Pit10() {
	// Go语言中对象的地址可能发生变化，因此指针不能从其它非指针类型的值生成：
	var x int = 42
	var p uintptr = uintptr(unsafe.Pointer(&x))

	runtime.GC()
	var px *int = (*int)(unsafe.Pointer(p))
	println(*px)
	// 当内存发生变化的时候，相关的指针会同步更新，但是非指针类型的uintptr不会做同步更新。
	// 同理CGO中也不能保存Go对象地址。
}

// 11、Goroutine泄露
func Pit11() {
	// Go语言是带内存自动回收的特性，因此内存一般不会泄漏。但是Goroutine确存在泄漏的情况，
	// 同时泄漏的Goroutine引用的内存同样无法被回收。
	// 错误演示
	//ch := func() <-chan int {
	//	ch := make(chan int)
	//	go func() {
	//		for i := 0; ; i++ {
	//			ch <- i
	//		}
	//	}()
	//	return ch
	//}()
	//
	//for v := range ch {
	//	fmt.Println(v)
	//	if v == 5 {
	//		break
	//	}
	//}

	// 正确演示
	ctx, cancel := context.WithCancel(context.Background())

	ch := func(ctx context.Context) <-chan int {
		ch := make(chan int)
		go func() {
			for i := 0; ; i++ {
				select {
				case <-ctx.Done():
					return
				case ch <- i:
				}
			}
		}()
		return ch
	}(ctx)

	for v := range ch {
		fmt.Println(v)
		if v == 5 {
			cancel()
			break
		}
	}
}
